﻿using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using AnimatingHair.Entity;
using AnimatingHair.Initializing;
using AnimatingHair.Rendering;
using AnimatingHair.Rendering.Debug;
using OpenTK;
using OpenTK.Graphics.OpenGL;
using System.IO;

namespace AnimatingHair
{
    public partial class ControlsWindow : Form
    {
        #region Members

        //debug:
        private VoxelGridRenderer voxelGridRenderer;

        private Scene scene;
        private SceneInitializer sceneInitializer;
        private IRenderable renderer;
        private SimpleRenderer simpleRenderer;
        private Renderer complexRenderer;
        private Camera camera;
        private Vector3d offset;
        private double distance = 10, elevation = 0, azimuth = 0, mouseX, mouseY;
        private bool mouseDown = false, zoomIn = false, zoomOut = false;
        private Random r = new Random();
        private bool loaded = false;
        private int frames = 0;
        private bool paused = false;
        private string saveFile = "";

        #endregion

        public ControlsWindow()
        {
            InitializeComponent();
        }

        protected override void OnLoad( EventArgs e )
        {
            try
            {
                base.OnLoad( e );

                checkOpenGLVersion();

                sceneInitializer = new SceneInitializer();
                scene = sceneInitializer.InitializeScene();
                camera = new Camera();
                simpleRenderer = new SimpleRenderer( camera, scene );
                complexRenderer = new Renderer( camera, scene );

                //debug:
                voxelGridRenderer = new VoxelGridRenderer( scene.VoxelGrid );

                useRenderer( simpleRenderer );

                offset = scene.Hair.CenterPosition();

                InitControls();

                Application.Idle += Application_Idle;

                loaded = true;
            }
            catch ( Exception exc )
            {
                MessageBox.Show( exc.Message );
                Close();
            }
        }

        private void useRenderer( IRenderable newRenderer )
        {
            renderer = newRenderer;
            renderer.Initialize();
        }

        private void InitControls()
        {
            glControl.KeyDown += glControl_KeyDown;
            glControl.KeyUp += glControl_KeyUp;
            glControl.Resize += glControl_Resize;
            glControl.Paint += glControl_Paint;

            Text =
                GL.GetString( StringName.Vendor ) + " " +
                GL.GetString( StringName.Renderer ) + " " +
                GL.GetString( StringName.Version );

            // Ensure that the viewport and projection matrix are set correctly.
            glControl_Resize( glControl, EventArgs.Empty );

            initializeTrackBars();

            fpsDisplayTimer.Enabled = true;
        }

        private void updateTextBoxes( object sender, EventArgs e )
        {
            textBoxGravity.Text = (trackBarGravity.Value / 1000.0).ToString();
            textBoxAirFriction.Text = (trackBarAirFriction.Value / 100.0).ToString();

            textBoxElasticModulus.Text = (trackBarElasticModulus.Value).ToString();
            textBoxCollisionDamping.Text = (trackBarCollisionDamping.Value / 100.0).ToString();
            textBoxFrictionDamping.Text = (trackBarFrictionDamping.Value / 100.0).ToString();
            textBoxAttractionRepulsion.Text = (trackBarAttractionRepulsion.Value / 10000.0).ToString();
            textBoxAverageDensity.Text = (trackBarAverageDensity.Value).ToString();

            textBoxNumberOfParticles.Text = trackBarNumberOfParticles.Value.ToString();
            textBoxHairLength.Text = (trackBarHairLength.Value / 10.0).ToString();
            textBoxMaxRootDepth.Text = (trackBarMaxRootDepth.Value / 10.0).ToString();
            textBoxDensityOfHairMaterial.Text = (trackBarDensityOfHairMaterial.Value / 10.0).ToString();
            textBoxParticleMass.Text = (trackBarParticleMass.Value / 10.0).ToString();

            updateConstants();
        }

        private void initializeTrackBars()
        {
            trackBarGravity.Value = Convert.ToInt32( Const.Gravity * 1000.0 );
            trackBarAirFriction.Value = Convert.ToInt32( Const.AirFriction * 100.0 );

            trackBarElasticModulus.Value = Convert.ToInt32( Const.E );
            trackBarCollisionDamping.Value = Convert.ToInt32( Const.d_c * 100.0 );
            trackBarFrictionDamping.Value = Convert.ToInt32( Const.d_f * 100.0 );
            trackBarAttractionRepulsion.Value = Convert.ToInt32( Const.k_a * 10000.0 );
            trackBarAverageDensity.Value = Convert.ToInt32( Const.rho_0 );

            numericUpDownSeed.Text = Const.Seed.ToString();
            trackBarNumberOfParticles.Value = Const.ParticleCount;
            trackBarHairLength.Value = Convert.ToInt32( Const.HairLength * 10.0 );
            trackBarMaxRootDepth.Value = Convert.ToInt32( Const.s_r * 10.0 );
            trackBarDensityOfHairMaterial.Value = Convert.ToInt32( Const.DensityOfHairMaterial * 10.0 );
            trackBarParticleMass.Value = Convert.ToInt32( Const.DefaultParticleMass * 10.0 );
        }

        private void updateConstants()
        {
            if ( !loaded )
                return;

            Const.Gravity = trackBarGravity.Value / 1000.0;
            Const.AirFriction = trackBarAirFriction.Value / 100.0;

            Const.E = trackBarElasticModulus.Value;
            Const.d_c = trackBarCollisionDamping.Value / 100.0;
            Const.d_f = trackBarFrictionDamping.Value / 100.0;
            Const.k_a = trackBarAttractionRepulsion.Value / 10000.0;
            Const.rho_0 = trackBarAverageDensity.Value;
        }

        private void polarToCartesian()
        {
            double x, y, z, k;
            double ele2 = elevation * Math.PI / 180;
            double azi2 = (azimuth - 90) * Math.PI / 180;

            k = Math.Cos( ele2 );
            z = distance * Math.Sin( ele2 );
            y = distance * Math.Sin( azi2 ) * k;
            x = distance * Math.Cos( azi2 ) * k;

            camera.Eye = new Vector3d( x, z, y );
            camera.Target = new Vector3d( 0, 0, 0 );
            camera.Up = Vector3d.UnitY;
        }

        private void checkOpenGLVersion()
        {
            string version = GL.GetString( StringName.Version );
            char major = version[ 0 ];
            if ( major < '2' )
            {
                MessageBox.Show( "You need at least OpenGL 2.0 to run this application. Aborting.", "GLSL not supported",
                    MessageBoxButtons.OK, MessageBoxIcon.Exclamation );
                Close();
            }
        }

        //private void sphereMovementControl()
        //{
        //    if ( Keyboard[ Key.Left ] )
        //    {
        //        scene.Bust.Center -= new Vector3d( 0.05f, 0, 0 );
        //        foreach ( HairParticle hp in scene.Hair.Particles )
        //        {
        //            if ( hp.IsRoot )
        //                hp.Position -= new Vector3d( 0.05f, 0, 0 );
        //        }
        //    }

        //    if ( Keyboard[ Key.Right ] )
        //    {
        //        scene.Bust.Center += new Vector3d( 0.05f, 0, 0 );
        //        foreach ( HairParticle hp in scene.Hair.Particles )
        //        {
        //            if ( hp.IsRoot )
        //                hp.Position += new Vector3d( 0.05f, 0, 0 );
        //        }
        //    }

        //    if ( Keyboard[ Key.Up ] )
        //    {
        //        scene.Bust.Center -= new Vector3d( 0, 0, 0.05f );
        //        foreach ( HairParticle hp in scene.Hair.Particles )
        //        {
        //            if ( hp.IsRoot )
        //                hp.Position -= new Vector3d( 0, 0, 0.05f );
        //        }
        //    }

        //    if ( Keyboard[ Key.Down ] )
        //    {
        //        scene.Bust.Center += new Vector3d( 0, 0, 0.05f );
        //        foreach ( HairParticle hp in scene.Hair.Particles )
        //        {
        //            if ( hp.IsRoot )
        //                hp.Position += new Vector3d( 0, 0, 0.05f );
        //        }
        //    }

        //    if ( Keyboard[ Key.PageUp ] )
        //    {
        //        scene.Bust.Center += new Vector3d( 0, 0.05f, 0 );
        //        foreach ( HairParticle hp in scene.Hair.Particles )
        //        {
        //            if ( hp.IsRoot )
        //                hp.Position += new Vector3d( 0, 0.05f, 0 );
        //        }
        //    }

        //    if ( Keyboard[ Key.PageDown ] )
        //    {
        //        scene.Bust.Center -= new Vector3d( 0, 0.05f, 0 );
        //        foreach ( HairParticle hp in scene.Hair.Particles )
        //        {
        //            if ( hp.IsRoot )
        //                hp.Position -= new Vector3d( 0, 0.05f, 0 );
        //        }
        //    }
        //}

        #region OnClosing

        protected override void OnClosing( CancelEventArgs e )
        {
            Application.Idle -= Application_Idle;

            base.OnClosing( e );
        }

        #endregion

        #region Application_Idle event

        void Application_Idle( object sender, EventArgs e )
        {
            while ( glControl.IsIdle )
            {
                Render();


                if ( zoomIn )
                    distance -= 0.05;

                if ( zoomOut )
                    distance += 0.05;

                //scene.Bust.Shoulders.Endpoint1 += 0.01 * shoulderMove * Vector3d.UnitZ;
                //scene.Bust.Shoulders.Endpoint2 += 0.01 * shoulderMove * Vector3d.UnitZ;
                //scene.Bust.ShoulderTipLeft.Center += 0.01 * shoulderMove * Vector3d.UnitZ;
                //scene.Bust.ShoulderTipRight.Center += 0.01 * shoulderMove * Vector3d.UnitZ;

                polarToCartesian();
                //camera.Eye += offset;
                //camera.Target += offset;
            }
        }

        #endregion

        #region GLControl.Resize event handler

        static void glControl_Resize( object sender, EventArgs e )
        {
            GLControl c = sender as GLControl;

            if ( c.ClientSize.Height == 0 )
                c.ClientSize = new Size( c.ClientSize.Width, 1 );

            GL.Viewport( 0, 0, c.ClientSize.Width, c.ClientSize.Height );

            float aspectRatio = c.AspectRatio;
            Matrix4 perpective = Matrix4.CreatePerspectiveFieldOfView( MathHelper.PiOver4, aspectRatio, 1, 64 );
            GL.MatrixMode( MatrixMode.Projection );
            GL.LoadMatrix( ref perpective );
        }

        #endregion

        #region GLControl Key event handlers

        void glControl_KeyUp( object sender, KeyEventArgs e )
        {
            switch ( e.KeyData )
            {
                case Keys.W:
                    zoomIn = false;
                    break;
                case Keys.S:
                    zoomOut = false;
                    break;
                case Keys.NumPad8:
                    scene.Up = false;
                    break;
                case Keys.NumPad5:
                    scene.Down = false;
                    break;
                case Keys.NumPad4:
                    scene.Left = false;
                    break;
                case Keys.NumPad6:
                    scene.Right = false;
                    break;
            }
        }

        void glControl_KeyDown( object sender, KeyEventArgs e )
        {
            switch ( e.KeyData )
            {
                case Keys.Escape:
                    Close();
                    break;
                case Keys.W:
                    zoomIn = true;
                    break;
                case Keys.S:
                    zoomOut = true;
                    break;
                case Keys.NumPad8:
                    scene.Up = true;
                    break;
                case Keys.NumPad5:
                    scene.Down = true;
                    break;
                case Keys.NumPad4:
                    scene.Left = true;
                    break;
                case Keys.NumPad6:
                    scene.Right = true;
                    break;
            }
        }

        #endregion

        #region GLControl.Paint event handler

        void glControl_Paint( object sender, PaintEventArgs e )
        {
            Render();
        }

        #endregion

        #region private void Render()

        private void Render()
        {
            renderer.Render();

            //debug:
            //voxelGridRenderer.Render();

            frames++;

            //Console.Clear();
            //Console.WriteLine( Const.Time );

            glControl.SwapBuffers();

            if ( !paused )
            {
                scene.Step();
                Const.Time++;
            }
        }

        #endregion

        private void glControl_MouseDown( object sender, MouseEventArgs e )
        {
            mouseDown = true;
        }

        private void glControl_MouseUp( object sender, MouseEventArgs e )
        {
            mouseDown = false;
        }

        private void glControl_MouseMove( object sender, MouseEventArgs e )
        {
            if ( mouseDown )
            {
                {
                    azimuth += 0.1 * (e.X - mouseX);
                    if ( azimuth > 360 )
                        azimuth -= 360;

                    elevation += 0.1 * (e.Y - mouseY);
                    if ( elevation > 89.99 )
                        elevation = 89.99;
                    if ( elevation < -89.99 )
                        elevation = -89.99;
                }
            }

            mouseX = e.X;
            mouseY = e.Y;
        }

        private void fpsDisplayTimer_Tick( object sender, EventArgs e )
        {
            FPSLabel.Text = "FPS: " + frames.ToString( "F0" );
            frames = 0;
        }

        private void button1_Click( object sender, EventArgs e )
        {
            updateRestartValues();
            restart();
        }

        private void restart()
        {
            scene = sceneInitializer.InitializeScene();
            camera = new Camera();
            simpleRenderer = new SimpleRenderer( camera, scene );
            complexRenderer = new Renderer( camera, scene );
            voxelGridRenderer = new VoxelGridRenderer( scene.VoxelGrid );
            checkBox1_CheckedChanged( this, null );
            checkBoxNeighborConnections_CheckedChanged( this, null );

            polarToCartesian();
        }

        private void updateRestartValues()
        {
            Const.Seed = int.Parse( numericUpDownSeed.Text );
            Const.ParticleCount = trackBarNumberOfParticles.Value;
            Const.HairLength = trackBarHairLength.Value / 10.0;
            Const.s_r = trackBarMaxRootDepth.Value / 10.0;
            Const.DensityOfHairMaterial = trackBarDensityOfHairMaterial.Value / 10.0;
            Const.DefaultParticleMass = trackBarParticleMass.Value / 10.0;
        }

        private void checkBox1_CheckedChanged( object sender, EventArgs e )
        {
            if ( checkBoxRendering.Checked )
            {
                checkBoxNeighborConnections.Enabled = true;
                useRenderer( simpleRenderer );
            }
            else
            {
                checkBoxNeighborConnections.Enabled = false;
                checkBoxNeighborConnections.Checked = false;
                useRenderer( complexRenderer );
            }
        }

        private void checkBoxNeighborConnections_CheckedChanged( object sender, EventArgs e )
        {
            simpleRenderer.RenderConnections = checkBoxNeighborConnections.Checked;
        }

        private void buttonRestartWithRandomSeed_Click( object sender, EventArgs e )
        {
            numericUpDownSeed.Text = r.Next( 1000 ).ToString();
            updateRestartValues();
            restart();
        }

        private void buttonPause_Click( object sender, EventArgs e )
        {
            paused = !paused;

            if ( paused )
                buttonPause.Text = "RESUME SIMULATION";
            else
                buttonPause.Text = "PAUSE SIMULATION";
        }

        private static string getConfigurationsDirectory()
        {
            string targetDirectory = Application.StartupPath + @"\Configurations";

            if ( !Directory.Exists( targetDirectory ) )
                Directory.CreateDirectory( targetDirectory );

            return targetDirectory;
        }

        private void saveConfigurationToolStripMenuItem_Click( object sender, EventArgs e )
        {
            // save

            if ( saveFile == "" )
            {
                saveFileDialog.InitialDirectory = getConfigurationsDirectory();
                if ( saveFileDialog.ShowDialog() == DialogResult.OK )
                {
                    saveFile = saveFileDialog.FileName;

                    Const.SaveConfiguration( saveFile );
                }
            }
            else
            {
                Const.SaveConfiguration( saveFile );
            }
        }

        private void loadConfigurationToolStripMenuItem_Click( object sender, EventArgs e )
        {
            // save as

            if ( saveFile != "" )
                saveFileDialog.FileName = saveFile.Substring( saveFile.LastIndexOf( '\\' ) + 1 );

            saveFileDialog.InitialDirectory = getConfigurationsDirectory();
            if ( saveFileDialog.ShowDialog() == DialogResult.OK )
            {
                saveFile = saveFileDialog.FileName;
                Const.SaveConfiguration( saveFile );
            }
        }

        private void loadToolStripMenuItem_Click( object sender, EventArgs e )
        {
            // load

            loaded = false;
            bool wasPaused = paused;
            paused = true;

            openFileDialog.InitialDirectory = getConfigurationsDirectory();
            if ( openFileDialog.ShowDialog() == DialogResult.OK )
            {
                saveFile = openFileDialog.FileName;
                Const.LoadConfiguration( saveFile );

                initializeTrackBars();
            }

            paused = wasPaused;
            restart();
            loaded = true;
        }

        private void exitToolStripMenuItem_Click( object sender, EventArgs e )
        {
            Close();
        }
    }
}
